package com.example.demo.netty;

import com.example.demo.utils.ExceptionUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

/**
 * Netty Server
 */
@Slf4j
@Getter
@Setter
public class NettyServer {

    private static final String TRUE = "true";
    private static final String FALSE = "false";

    private int port;
    private String name;
    private int bossSize;
    private int workerSize;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private ServerBootstrap bootstrap;
    private AbstractServerHandler initializer;

    private ChannelFuture future;

    public NettyServer(AbstractServerHandler initializer) {
        this.initializer = initializer;
    }

    public void init() {
        this.bossGroup = new NioEventLoopGroup(bossSize);
        this.workerGroup = new NioEventLoopGroup(workerSize);
        this.bootstrap = new ServerBootstrap().group(bossGroup, workerGroup).channel(NioServerSocketChannel.class);
        if (null == initializer) {
            throw new IllegalArgumentException("initializer is null!");
        }
        bootstrap.childHandler(initializer);
    }

    public void start() {
        try {
            future = bootstrap.bind(port).sync();
            log.info("started netty server. channel:{}, serverName:{}, port:{}", future.channel().id().asShortText(),
                    this.getName(), this.getPort());
        } catch (Exception e) {
            stop(ExceptionUtil.getStackTrace(e));
        } finally {
            Runtime.getRuntime().addShutdownHook(new Thread(this::stop));
        }
    }

    public void stop() {
        stop("application has closed!");
    }

    public void stop(String cause) {
        String id = null;
        if (future != null && future.channel() != null) {
            id = future.channel().id().asShortText();
            future.channel().disconnect().syncUninterruptibly();
            future.channel().close().syncUninterruptibly();
        }
        closeNettyService(this.getName(), bossGroup);
        closeNettyService(this.getName(), workerGroup);
        log.error("stopped netty server. channelId:{}, serverName:{}, port:{}, cause:{}", id, this.getName(),
                this.getPort(), cause);
    }

    private void closeNettyService(String serverName, EventLoopGroup eventLoopGroup) {
        try {
            if (eventLoopGroup != null && eventLoopGroup.isShutdown()) {
                eventLoopGroup.shutdownGracefully();
            }
        } catch (Exception e) {
            log.error("close netty service {} exception.", serverName, e);
        }
    }
}
